const std = @import("std");
const os = @import("os.zig");

pub const ether = @import("net/ether.zig");
pub const arp = @import("net/arp.zig");
pub const ip = @import("net/ip.zig");
pub const udp = @import("net/udp.zig");
pub const dns = @import("net/dns.zig");

pub const MacLen = 6;
pub const Mac = [MacLen]u8;

/// 10.0.2.15
pub const local_ip: u32 = (10 << 24) + (0 << 16) + (2 << 8) + 15;
// xv6's ethernet addresses
pub const local_mac: Mac = .{ 0x52, 0x54, 0x00, 0x12, 0x34, 0x56 };
/// qemu host's ethernet address.
pub const host_mac: [MacLen]u8 = .{ 0x52, 0x55, 0x0a, 0x00, 0x02, 0x02 };

/// Conditionally byte-swaps all fields of a struct based on system endianness.
/// This is useful for converting network protocol structs between host and network byte order.
pub fn swapAllFields(ptr: anytype) void {
    if (os.native_endian == .big) return;
    const info = @typeInfo(@TypeOf(ptr));
    std.mem.byteSwapAllFields(info.pointer.child, ptr);
}

/// Converts packed struct field order to match network protocol bit ordering.
pub fn NetBitOrder(S: type) type {
    const s_info = @typeInfo(S).@"struct";
    if (s_info.layout != .@"packed" or s_info.backing_integer == null)
        @compileError("expects a packed struct");

    const StructField = std.builtin.Type.StructField;
    var new_fields: [s_info.fields.len]StructField = undefined;
    comptime var start = 0;
    comptime var bits = 0;

    inline for (s_info.fields, 0..) |f, i| {
        bits += @bitSizeOf(f.type);

        // When we've accumulated exactly 8 bits (1 byte)
        if (bits == @bitSizeOf(u8)) {
            const end = i + 1;
            const sub_fields = s_info.fields[start..end];

            // Reverse fields within this byte group
            inline for (sub_fields, 0..) |sf, j| {
                new_fields[start..end][sub_fields.len - 1 - j] = sf;
            }

            // Reset tracking for next byte group
            start = end;
            bits = 0;
        } else if (bits > @bitSizeOf(u8)) {
            @compileError("unsupported struct");
        }
    }

    var names: [new_fields.len][:0]const u8 = undefined;
    var types: [new_fields.len]type = undefined;
    var attrs: [new_fields.len]StructField.Attributes = undefined;
    for (new_fields, &names, &types, &attrs) |field, *name, *typ, *attr| {
        name.* = field.name;
        typ.* = field.type;
        attr.* = .{
            .@"align" = null, // packed struct fields cannot be aligned
            .@"comptime" = field.is_comptime,
            .default_value_ptr = field.default_value_ptr,
        };
    }
    return @Struct(.@"packed", s_info.backing_integer, &names, &types, &attrs);
}

pub fn send(packet: []u8) !void {
    try os.driver.e1000.transmit(packet);
}

pub fn recv(packet: []u8) !void {
    try ether.recv(packet);
}
